#include "cluster.hpp"

Cluster::Cluster() {
}

Cluster::Cluster(const Slice &slice) {
  assert((slice.size().array()>=1).all());
  clusterSlice=slice;
  calculateCentreOfGravity();
}

Cluster::Cluster(ClusterNode *node) {
  this->node=node;
  node->setCluster(this);
}

void Cluster::setNode(ClusterNode *node) {
  this->node=node;
  node->setCluster(this);
}

ClusterNode * Cluster::getNode() const {
  return node;
}

const Vector3d & Cluster::getCentreOfGravity() const {
  return cog;
}

void Cluster::scoop(Slice &volumeSlice) {
  calculateScoopDistance();
  /*
   * - set up holding slice
   * - insert existing slice
   * - run sphere mask
   * - label components
   * - find label of existing slice
   * - create new slice from bounding box of label
   * - copy component into new slice
   */
  int sizeHalf=static_cast<int>(scoopDistance)+1;
  Vector3i tmpSizeHalf;
  tmpSizeHalf << sizeHalf,sizeHalf,sizeHalf;

  Vector3i tmpOffset=(node->getPosition().cast<int>()-tmpSizeHalf).cwiseMax(Vector3i(0,0,0));
  Vector3i tmpOffsetEnd=(node->getPosition().cast<int>()+Vector3i(1,1,1)+tmpSizeHalf).cwiseMin(volumeSlice.size());

  Slice tmp(tmpOffsetEnd-tmpOffset,0);
  tmp.setOffset(tmpOffset);
  Vector3i sliceStart=clusterSlice.getOffset()-tmp.getOffset();
  tmp.slice(sliceStart,sliceStart+clusterSlice.size())=clusterSlice;

  Slice sphereSlice=volumeSlice.slice(tmp.getStart()+tmp.getOffset(),tmp.size()+tmp.getOffset());
  SliceIterator sphereIt(sphereSlice,tmp);
  SliceUtils::SphereParam sphereParam;
  sphereParam.centre=node->getPosition()-tmp.getOffset().cast<double>();
  sphereParam.radius=scoopDistance;
  sphereIt.execute(SliceUtils::sphereMask,&sphereParam);

  Slice labelled(tmp.size(),0);
  labelled.setOffset(tmp.getOffset());
  SliceUtils::LabelParam labelParam;
  labelParam.offsets.clear();
  SliceIterator labelIt(tmp,labelled);
  labelIt.execute(SliceUtils::label,&labelParam);

  Slice findLabelSlice=labelled.slice(clusterSlice.getOffset()-labelled.getOffset(),
      clusterSlice.getOffset()+clusterSlice.size()-labelled.getOffset());
  SliceIterator findLabelIt(findLabelSlice,clusterSlice);
  unsigned char label=0;
  while (findLabelIt.hasNext() and label==0) {
    findLabelIt.next();
    if (findLabelIt.secondValue()!=0) {
      label=findLabelIt.firstValue();
    }
  }

  SliceIterator bbIt(labelled);
  SliceUtils::BoundingBoxParam bbParam;
  bbParam.min=labelled.getEnd();
  bbParam.max=labelled.getStart();
  bbParam.label=label;
  bbIt.execute(SliceUtils::boundingBox,&bbParam);

  Slice newSlice(bbParam.max-bbParam.min,0);
  newSlice.setOffset(labelled.getOffset()+bbParam.min);
  Slice bbSlice(labelled.slice(bbParam.min,bbParam.max));
  SliceIterator filterIt(bbSlice,newSlice);
  SliceUtils::FilterParam filterParam;
  filterParam.srcValue=label;
  filterParam.destValue=1;
  filterIt.execute(SliceUtils::filter,&filterParam);

  Slice volumeSliceView=volumeSlice.slice(newSlice.getOffset(),newSlice.getOffset()+newSlice.size());
  SliceIterator unsetIt(volumeSliceView,newSlice);
  unsetIt.execute(SliceUtils::unset);
  
  clusterSlice.replace(newSlice);
}

void Cluster::calculateScoopDistance() {
  SliceIterator it(clusterSlice);
  SliceUtils::MaxDistanceParam mdParam;
  mdParam.point=node->getPosition()-clusterSlice.getOffset().cast<double>();
  it.execute(SliceUtils::maxDistanceToPoint,&mdParam);
  scoopDistance=mdParam.distance;
}

void Cluster::calculateCentreOfGravity() {
  cog << 0.5,0.5,0.5;
  if ((clusterSlice.size().array()>1).any()) {
    SliceUtils::CoGParam cogParam;
    cogParam.num=0;
    cogParam.accumulator << 0,0,0;
    SliceIterator it(clusterSlice);
    it.execute(SliceUtils::cog,&cogParam);
    cog+=(cogParam.accumulator).cast<double>()/cogParam.num;
  }
  cog+=clusterSlice.getOffset().cast<double>();
}

void Cluster::setSize(double size) {
  this->size=size;
}

double Cluster::getSize() const {
  return size;
}

const Vector3i & Cluster::getOffset() const {
  return offset;
}

void Cluster::setOffset(const Vector3i &offset) {
  this->offset=offset;
}

const Slice & Cluster::slice() const {
  return clusterSlice;
}
